/*
 * BinomialIdeal.java
 *
 * Created on June 15, 2005, 10:29 AM
 *
 * (C) R. Alexander Milowski alex@milowski.com
 */

package com.milowski.monos.binomial;

import java.io.Writer;
import java.util.*;

import com.milowski.monos.monomial.*;
import com.milowski.monos.*;

import com.milowski.monos.tools.Shell;
import org.infoset.xml.Element;
import org.infoset.xml.InfosetFactory;
import org.infoset.xml.ItemConstructor;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.Name;
import org.infoset.xml.XMLException;

/**
 *
 * @author R. Alexander Milowski
 */
public class BinomialIdeal extends LexOrderedList implements XMLMarshaller
{
   
   /** Creates a new instance of BinomialIdeal */
   public BinomialIdeal(LexOrder order)
   {
      super(order);
   }
   
   public boolean add(Object o) {
      if (!(o instanceof Binomial)) {
         throw new ClassCastException("Only Binomial instances are allowed in a monomial ideal: "+o.getClass().getName());
      }
      return super.add(o);
   }
   
   public void add(int index,Object o) {
      if (!(o instanceof Binomial)) {
         throw new ClassCastException("Only Binomial instances are allowed in a monomial ideal: "+o.getClass().getName());
      }
      super.add(index,o);
   }
   
   private void checkCollection(Collection c) {
      for (Iterator toCheck = c.iterator(); toCheck.hasNext(); ) {
         Object o = toCheck.next();
         if (!(o instanceof Binomial)) {
            throw new ClassCastException("Only Binomial instances are allowed in a monomial ideal: "+o.getClass().getName());
         }
      }
   }
   
   public boolean addAll(Collection c) {
      checkCollection(c);
      return super.addAll(c);
   }
   
   public boolean addAll(int index,Collection c) {
      checkCollection(c);
      return super.addAll(index,c);
   }
   
   public Object set(int index,Object o) {
      if (!(o instanceof Binomial)) {
         throw new ClassCastException("Only Binomial instances are allowed in a monomial ideal: "+o.getClass().getName());
      }
      return super.set(index, o);
   }
   
   public MonomialIdeal getInitialIdeal() {
      MonomialIdeal initial = new MonomialIdeal(order);
      for (Iterator toAdd=iterator(); toAdd.hasNext(); ) {
         Binomial b = (Binomial)toAdd.next();
         initial.add(b.toLeadingMonomial());
      }
      return initial;
   }
   
   public String toString() {
      return toString(null);
   }
   
   public String toString(LexOrder inScopeOrder) {
      StringBuffer sb = new StringBuffer();
      if (inScopeOrder!=order) {
         sb.append("(binomial-ideal-with-order ");
         sb.append(order.toString());
      } else {
         sb.append("(binomial-ideal ");
      }
      for (Iterator toOutput=iterator(); toOutput.hasNext();) {
         Binomial b = (Binomial)toOutput.next();
         sb.append(' ');
         sb.append('(');
         sb.append(b.format(order));
         sb.append(')');
      }              
      sb.append(")");
      return sb.toString();
   }
   
   public void toXML(ItemDestination dest) throws XMLException {
      toXML(InfosetFactory.getDefaultInfoset().createItemConstructor(),dest);
   }

   public void toXML(ItemConstructor constructor, ItemDestination dest) throws XMLException {
      toXML(false,false,constructor,dest);
   }
   
   protected String formatBinomial(boolean compact,short [] values) {
      if (compact) {
         StringBuffer sb = new StringBuffer();
         for (int i=0; i<values.length; i++) {
            if (i!=0) {
               sb.append(' ');
            }
            sb.append(Short.toString(values[i]));
         }
         return sb.toString();
      } else {
         StringBuffer l = new StringBuffer();
         StringBuffer t = new StringBuffer();
         for (int i=0; i<values.length; i++) {
            if (i!=0) {
               l.append(' ');
            }
            t.append(' ');
            if (values[i]<0) {
               l.append(" 0");
               t.append(Integer.toString(-values[i]));
            } else {
               l.append(Short.toString(values[i]));
               t.append(" 0");
            }
         }
         return l.toString()+t.toString();
      }
   }
   
   protected String formatBinomial(boolean compact,short [] leading,short [] trailing) {
      if (compact) {
         StringBuffer sb = new StringBuffer();
         for (int i=0; i<leading.length; i++) {
            if (i!=0) {
               sb.append(' ');
            }
            sb.append(Integer.toString(leading[i]-trailing[i]));
         }
         return sb.toString();
      } else {
         StringBuffer l = new StringBuffer();
         StringBuffer t = new StringBuffer();
         for (int i=0; i<leading.length; i++) {
            if (i!=0) {
               l.append(' ');
            }
            l.append(Short.toString(leading[i]));
            t.append(' ');
            t.append(Short.toString(trailing[i]));
         }
         return l.toString()+t.toString();
      }
   }
   
   public void toXML(boolean matrix,boolean compact,ItemConstructor constructor, ItemDestination dest) throws XMLException {
      dest.send(constructor.createElement(XML.binomialIdealName));
      dest.send(constructor.createCharacters("\n"));
      order.toXML(constructor,dest);
      dest.send(constructor.createCharacters("\n"));
      if (matrix) {
         Element start = constructor.createElement(XML.matrixName);
         if (compact) {
            start.setAttributeValue("compact","true");
         }
         dest.send(start);
         dest.send(constructor.createCharacters("\n"));
         for (Iterator binomials=iterator(); binomials.hasNext(); ) {
            Binomial b = (Binomial)binomials.next();
            dest.send(constructor.createElement(XML.ilistName));
            String value;
            if (b instanceof CompactBinomial) {
               value = formatBinomial(compact,((CompactBinomial)b).getValues());
            } else {
               value = formatBinomial(compact,b.getLeadingTerm(),b.getTrailingTerm());
            }
            dest.send(constructor.createCharacters(value));
            dest.send(constructor.createElementEnd(XML.ilistName));
            dest.send(constructor.createCharacters("\n"));
         }
         dest.send(constructor.createElementEnd(XML.matrixName));
         dest.send(constructor.createCharacters("\n"));
      } else {
         for (Iterator binomials=iterator(); binomials.hasNext(); ) {
            Binomial b = (Binomial)binomials.next();
            if (compact) {
               b = b.toCompactBinomial();
            }
            b.toXML(constructor,dest);
         }
      }
      dest.send(constructor.createElementEnd(XML.binomialIdealName));
      dest.send(constructor.createCharacters("\n"));
   }
   
   public static BinomialIdeal fromXML(Element representation) 
      throws XMLException
   {
      return fromXML(representation,false);
   }
   
   public static BinomialIdeal fromXML(Element representation,boolean expandToFull) 
      throws XMLException
   {
      if (!representation.getName().equals(XML.binomialIdealName)) {
         throw new XMLException("Cannot unmarshall binomial ideal from "+representation.getName());
      }
      Iterator<Element> children = representation.getElementChildren();
      if (!children.hasNext()) {
         throw new XMLException("Missing "+XML.lexOrderName+" for "+XML.binomialIdealName);
      }
      Element lexOrderRep = children.next();
      LexOrder lexOrder = null;
      if (lexOrderRep.getName().equals(XML.expressionName)) {
         if (children.hasNext()) {
            throw new XMLException((children.next()).getName()+" unexpected in "+XML.binomialIdealName);
         }
         // TODO: expression should be checked for compact binomials
         try {
            Shell.init();
            BinomialIdeal ideal = (BinomialIdeal)Shell.eval(lexOrderRep.getText());
            return ideal;
         } catch (Throwable ex) {
            throw new XMLException("Cannot evaluate monomial ideal expression.",ex);
         }
      } 

      if (lexOrderRep.getName().equals(XML.lexOrderName)) {
         lexOrder = (LexOrder)XML.unmarshall(lexOrderRep);
         if (!children.hasNext()) {
            throw new XMLException("Expecting "+XML.matrixName+" or a binomial.");
         }
         Element next = children.next();
         Name nm = next.getName();
         if (nm.equals(XML.matrixName)) {
            MatrixBasis matrix = MatrixBasis.fromXML(next);
            if (children.hasNext()) {
               throw new XMLException("Unexpected element "+(children.next()).getName());
            }
            if (expandToFull) {
               return matrix.toBinomials(false);
            } else {
               return matrix.toBinomials();
            }
         } else {
            BinomialIdeal ideal = new BinomialIdeal(lexOrder);
            Binomial b = (Binomial)XML.unmarshall(next);
            if (expandToFull && !(b instanceof FullBinomial)) {
               b = new FullBinomial(b.getLeadingTerm(),b.getTrailingTerm(), false);
            }
            ideal.add(b);
            while (children.hasNext()) {
               b = (Binomial)XML.unmarshall((Element)children.next());
               if (expandToFull && !(b instanceof FullBinomial)) {
                  b = new FullBinomial(b.getLeadingTerm(),b.getTrailingTerm(), false);
               }
               ideal.add(b);
            }
            return ideal;
         }
      } else {
         MatrixBasis matrix = MatrixBasis.fromXML(lexOrderRep);
         if (children.hasNext()) {
            throw new XMLException("Unexpected element "+(children.next()).getName());
         }
         if (expandToFull) {
            return matrix.toBinomials(false);
         } else {
            return matrix.toBinomials();
         }
      }
   }
   
   public void writeAsMatrix(Writer out)
      throws java.io.IOException
   {
      writeAsMatrix(out,true,null);
   }
   
   public void writeAsMatrix(Writer out,boolean compact)
      throws java.io.IOException
   {
      writeAsMatrix(out,compact,null);
   }
   public void writeAsMatrix(Writer out,boolean compact,int [] reorder)
      throws java.io.IOException
   {
      out.write(Integer.toString(size()));
      out.write(' ');
      out.write(Integer.toString(order.size()));
      out.write('\n');
      Iterator elements = iterator();
      while (elements.hasNext()) {
         Binomial b = (Binomial)elements.next();
         if (b instanceof CompactBinomial) {
            short [] values = ((CompactBinomial)b).getValues();

            if (compact) {
               for (int e=0; e<values.length; e++) {
                  int index = reorder==null ? e : reorder[e];
                  if (values[index]>=0) {
                     out.write(' ');
                  }
                  out.write(Short.toString(values[index]));
                  out.write(' ');
               }
            } else {
               for (int e=0; e<values.length; e++) {
                  int index = reorder==null ? e : reorder[e];
                  out.write(values[index]>0 ? Short.toString(values[index]) : "0");
                  out.write(' ');
               }
               for (int e=0; e<values.length; e++) {
                  int index = reorder==null ? e : reorder[e];
                  out.write(values[index]<0 ? Integer.toString(-values[index]) : "0");
                  out.write(' ');
               }
            }
         } else {
            short [] lvalues = b.getLeadingTerm();
            short [] tvalues = b.getTrailingTerm();

            if (compact) {
               for (int e=0; e<lvalues.length; e++) {
                  int index = reorder==null ? e : reorder[e];
                  int value = lvalues[index]-tvalues[index];
                  if (value>=0) {
                     out.write(' ');
                     out.write(Integer.toString(value));
                  } else {
                     out.write(Integer.toString(value));
                  }
                  out.write(' ');
               }
            } else {
               for (int e=0; e<lvalues.length; e++) {
                  int index = reorder==null ? e : reorder[e];
                  out.write(Short.toString(lvalues[index]));
                  out.write(' ');
               }
               for (int e=0; e<tvalues.length; e++) {
                  int index = reorder==null ? e : reorder[e];
                  out.write(Short.toString(tvalues[index]));
                  out.write(' ');
               }
            }
         }
         out.write('\n');
      }
      out.flush();
   }
   
}
